#include <string.h>

#include "gkt_board.h"
#include "gkt_debug.h"
#include "flash_common.h"
#include "gkt_flash_partition.h"

#define FLASH_PARTITION_RDONLY		0
#define FLASH_PARTITION_WRITABLE	1

typedef struct _flash_partition {
	const char	*name;	
	uint32_t	device_id;	/* device id */
	uint32_t	offset;	/* offset in device */
	int	writable;

	gkt_flash_partition_info_s	info;
} flash_partition_s;

#define GKT_FLASH_PARTITION(part_name, device_name, part_offset, part_size, rw_perm)	\
	{	\
		.name	= part_name,	\
		.device_id	= GKT_##device_name##_ID,	\
		.offset	= part_offset,	\
		.writable	= FLASH_PARTITION_##rw_perm,	\
		.info	= {	\
			.id	= -1,	\
			.size	= (uint32_t)(part_size),	\
			.read_map_addr	= NULL,	\
		},	\
	}

static flash_partition_s s_flash_partition_table[] = 
	{ GKT_CONFIG_FLASH_PARTITION_TABLE };
static const uint32_t sc_flash_partition_table_size = 
	sizeof(s_flash_partition_table) / sizeof(flash_partition_s);

int gkt_flash_partition_init(void)
{
	flash_partition_s *partition;
	const gkt_flash_info_s *flash_info = NULL;
	uint32_t device_offset[GKT_FLASH_DEVICE_NUMS];
	uint32_t device_id, aligned_size, aligned_offset;
	uint32_t i;

	/* init  */
	for (i = 0; i < GKT_FLASH_DEVICE_NUMS; i++)
		device_offset[i] = 0;

	device_id = ~0;
	for (i = 0; i < sc_flash_partition_table_size; i++) {
		partition = &s_flash_partition_table[i];
		GKT_ASSERT(partition->name != NULL);
		GKT_ASSERT(partition->device_id < GKT_FLASH_DEVICE_NUMS);
		GKT_ASSERT(partition->info.size != 0);

		if (!flash_info 
			|| (partition->device_id != device_id)) 
		{
#if (1 == GKT_FLASH_DEVICE_NUMS)		
			flash_info = gkt_flash_get_info(partition->device_id);
#else
			flash_device_s *device;

			device = FLASH_DEVICE(partition->device_id);
			flash_info = device->info;
#endif
			if (__UNLIKELY(!flash_info)) {
				gkt_error("flash_partition_init: device(%u) not exist.\n", partition->device_id);
				return GKT_ENOTEXIST;
			}
			device_id = partition->device_id;
		}

		if (partition->info.size != (uint32_t)-1)
			aligned_size = gkt_aligned(partition->info.size, flash_info->sector_size);
		else	/* the rest of the space of the flash device */
			aligned_size = flash_info->total_size - device_offset[partition->device_id];
		GKT_ASSERT(aligned_size > 0);

		if (!partition->offset)
			aligned_offset = device_offset[partition->device_id];
		else {
			aligned_offset = gkt_aligned(partition->offset, flash_info->sector_size);
			GKT_ASSERT(device_offset[partition->device_id] <= aligned_offset);
		}
		device_offset[partition->device_id] = aligned_offset + aligned_size;
		GKT_ASSERT(device_offset[partition->device_id] <= flash_info->total_size); 	

		partition->offset = aligned_offset;
		partition->info.id = (int)i;
		partition->info.sector_size = flash_info->sector_size;
		partition->info.size = aligned_size;
		partition->info.read_map_addr = gkt_flash_map(partition->device_id, 
							partition->offset, partition->info.size);
		gkt_trace("flash_partition: (%s), dev_id(%u), offset(0x%x), size(0x%x)\n", 
							partition->name, partition->device_id, 
							partition->offset, partition->info.size);
	}

	return GKT_SUCCESS;
}

const gkt_flash_partition_info_s 
	*gkt_flash_partition_get_info(const char *name)
{
	flash_partition_s *partition;
	uint32_t id;

	if (name) {
		for (id = 0; id < sc_flash_partition_table_size; id++) {
			partition = &s_flash_partition_table[id];
			if (!strcmp(name, partition->name))
				return (const gkt_flash_partition_info_s  *)&partition->info; 
		}
	}

	return NULL;
}

int gkt_flash_partition_read(int id,
			uint32_t offset, uint32_t size, 
			void *buffer)
{
	flash_partition_s *partition;
	int retval = GKT_EPARAM;

	if ((id >= 0) 
		&& (id < (int)sc_flash_partition_table_size)
		&& buffer && size)
	{
		partition = &s_flash_partition_table[id];
		if ((offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size))
		{
			retval = gkt_flash_read(partition->device_id,
						partition->offset + offset, size, buffer);
		}
	}

	return retval;
}

int gkt_flash_partition_write(int id,
			uint32_t offset, uint32_t size, 
			const void *data, int pre_erase)
{
	flash_partition_s *partition;
	int retval = GKT_EPARAM;

	if ((id >= 0) 
		&& (id < (int)sc_flash_partition_table_size)
		&& data && size)
	{
		partition = &s_flash_partition_table[id];
		if ((offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size))
		{
			if (pre_erase) {
				retval = gkt_flash_erase(partition->device_id,
								partition->offset + offset, size);
				if (retval != GKT_SUCCESS)
					return retval;
			}
		
			retval = gkt_flash_write(partition->device_id,
						partition->offset + offset, size, data);
		}
	}

	return retval;
}


int gkt_flash_partition_erase(int id,
			uint32_t offset, uint32_t size)
{
	flash_partition_s *partition;
	int retval = GKT_EPARAM;

	if ((id >= 0) 
		&& (id < (int)sc_flash_partition_table_size)
		&& size)
	{
		partition = &s_flash_partition_table[id];
		if ((offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size)) 
		{
			retval = gkt_flash_erase(partition->device_id, 
							partition->offset + offset, size);
		}
	}

	return retval;
}

int gkt_flash_partition_erase_all(int id)
{
	flash_partition_s *partition;
	int retval = GKT_EPARAM;

	if ((id >= 0) 
		&& (id < (int)sc_flash_partition_table_size))
	{
		partition = &s_flash_partition_table[id];
		if (partition->writable) {
			retval = gkt_flash_erase(partition->device_id, 
						partition->offset, partition->info.size);
		}
		else
			retval = GKT_EPERMIT;
	}
	
	return retval;
}

